#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h> //isspace
#include <stdlib.h>

#define NUM 1024
#define OPT_NUM 64

char commandline[NUM]; // 指令数组
char *myargv[OPT_NUM]; // 指针数组，用来保存切割后的字符
int lastsig = 0;       // 退出信号
int lastcode = 0;      // 退出码

// 定义重定向相关的宏
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

#define trimspace(start)        \
    do                          \
    {                           \
        while (isspace(*start)) \
            ++start;            \
    } while (0)

int redirtype = NONE_REDIR; // 重定向的类型默认为无重定向
char *redirfile = NULL;     // 重定向的文件的起始位置

// "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
void commandcheck(char *command)
{
    assert(command);
    char *start = command;
    char *end = start + strlen(command);

    while (start < end)
    {
        if (*start == '>')
        {
            *start = '\0';
            start++;
            if (*start == '>')
            {
                // "ls -a >> file.log"
                redirtype = APPEND_REDIR;
                start++;
            }
            else
            {
                // "ls -a >    file.log"
                redirtype = OUTPUT_REDIR;
            }

            trimspace(start);
            redirfile = start;
            break;
        }
        else if (*start == '<')
        {
            //"cat <      file.txt"
            *start = '\0';
            start++;
            trimspace(start);
            redirtype = INPUT_REDIR;
            redirfile = start;
            break;
        }
        else
        {
            start++;
        }
    }
}

int main()
{
    while (1)
    {
        redirtype = NONE_REDIR;
        redirfile = NULL;
        errno = 0;

        // 输出提示符
        printf("用户名@主机名 当前路径#");
        fflush(stdout);

        // 获取用户输入, 输入的时候，输入\n
        char *s = fgets(commandline, sizeof(commandline) - 1, stdin);
        assert(s != NULL);
        (void)s;

        // 清除最后一个\n , abcd\n
        commandline[strlen(s) - 1] = 0;

        // "ls -a -l -i" -> "ls" "-a" "-l" "-i" -> 1->n
        // "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
        // "ls -a -l -i >> myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
        // "cat < myfile.txt" -> "cat" "myfile.txt" ->
        commandcheck(commandline);
        myargv[0] = strtok(commandline, " ");

        int i = 1;
        if (myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
        {
            myargv[i++] = (char *)"--color=auto";
        }

        // 如果没有子串了，strtok->NULL, myargv[end] = NULL
        while (myargv[i++] = strtok(NULL, " "))
            ;

        // 如果是cd命令，不需要创建子进程,让shell自己执行对应的命令，本质就是执行系统接口
        // 像这种不需要让我们的子进程来执行，而是让shell自己执行的命令 --- 内建/内置命令
        if (myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
        {
            if (myargv[1] != NULL)
            {
                chdir(myargv[1]);
            }
            continue;
        }

        if (myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
        {
            if (strcmp(myargv[1], "$?") == 0)
            {
                printf("%d,%d\n", lastcode, lastsig);
            }
            else
            {
                printf("%s\n", myargv[1]);
            }
            continue;
        }

        // 执行命令
        pid_t id = fork();
        assert(id != -1);

        if (id == 0)
        {
            // 因为命令是子进程执行的，真正重定向的工作一定要是子进程来完成
            // 如何重定向，是父进程要给子进程提供信息的
            // 这里重定向会影响父进程吗？不会，进程具有独立性
            switch (redirtype)
            {
            case NONE_REDIR:
                // 什么都不做
                break;
            case INPUT_REDIR:
            {
                int fd = open(redirfile, O_RDONLY);
                if (fd < 0)
                {
                    perror("open failed");
                    exit(errno);
                }
                // 重定向的文件已经成功打开了
                dup2(fd, 0);
            }
            break;
            case OUTPUT_REDIR:
            case APPEND_REDIR:
            {
                umask(0);
                int flags = O_WRONLY | O_CREAT;
                if (redirtype == APPEND_REDIR)
                    flags |= O_APPEND;
                else
                    flags |= O_TRUNC;
                int fd = open(redirfile, flags, 0666);
                if (fd < 0)
                {
                    perror("open");
                    exit(errno);
                }
                dup2(fd, 1);
            }
            break;
            default:
                printf("bug?\n");
                break;
            }

            execvp(myargv[0], myargv); // 执行程序替换的时候，会不会影响曾经进程打开的重定向的文件？不会
            exit(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        assert(ret > 0);
        (void)ret;
        lastcode = ((status >> 8) & 0xFF);
        lastsig = (status & 0x7F);
    }
}